/*****************************************************************************
 * plum_kernel_backtrace.c
 *
 * Copyright (C) 2020 Jeasonvor 1101627719@qq.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/
#if 0
/*****************************************************************************
 * Included Files
 ****************************************************************************/
#include "kernel/include/plum_kernel_api.h"

/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/*****************************************************************************
 * Private Types
 ****************************************************************************/

/** dump类型 */
typedef enum {
    PLUM_MAIN_STACK_CFG_ERROR,
    PLUM_FIRMWARE_INFO,
    PLUM_ASSERT_ON_THREAD,
    PLUM_ASSERT_ON_HANDLER,
    PLUM_THREAD_STACK_INFO,
    PLUM_MAIN_STACK_INFO,
    PLUM_THREAD_STACK_OVERFLOW,
    PLUM_MAIN_STACK_OVERFLOW,
    PLUM_CALL_STACK_INFO,
    PLUM_CALL_STACK_ERR,
    PLUM_FAULT_ON_THREAD,
    PLUM_FAULT_ON_HANDLER,
    PLUM_REGS_TITLE,
    PLUM_HFSR_VECTBL,
    PLUM_MFSR_IACCVIOL,
    PLUM_MFSR_DACCVIOL,
    PLUM_MFSR_MUNSTKERR,
    PLUM_MFSR_MSTKERR,
    PLUM_MFSR_MLSPERR,
    PLUM_BFSR_IBUSERR,
    PLUM_BFSR_PRECISERR,
    PLUM_BFSR_IMPREISERR,
    PLUM_BFSR_UNSTKERR,
    PLUM_BFSR_STKERR,
    PLUM_BFSR_LSPERR,
    PLUM_UFSR_UNDEFINSTR,
    PLUM_UFSR_INVSTATE,
    PLUM_UFSR_INVPC,
    PLUM_UFSR_NOCP,
#if (CPU_PLATFORM_TYPE == CORTEX_M33)
    PLUM_UFSR_STKOF,
#endif
    PLUM_UFSR_UNALIGNED,
    PLUM_UFSR_DIVBYZERO0,
    PLUM_DFSR_HALTED,
    PLUM_DFSR_BKPT,
    PLUM_DFSR_DWTTRAP,
    PLUM_DFSR_VCATCH,
    PLUM_DFSR_EXTERNAL,
    PLUM_MMAR,
    PLUM_BFAR,
}trace_dumpcode_t;

/** dump 描述 */
typedef struct{
    trace_dumpcode_t    code;
    plum_s8             *discrib;
}trace_dumpdisc_t;

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

PLUM_PRIVATE
plum_kernel_print_t print = plum_null;

PLUM_PRIVATE   
trace_dumpdisc_t   dump[] = {
    {PLUM_MAIN_STACK_CFG_ERROR, "ERROR: Unable to get the main stack information, please check the configuration of the main stack"},
    {PLUM_FIRMWARE_INFO , "Firmware name: %s, hardware version: %s, software version: %s"},
    {PLUM_ASSERT_ON_THREAD, "Assert on thread %s"},
    {PLUM_ASSERT_ON_HANDLER,"Assert on interrupt or bare metal(no OS) environment"},
    {PLUM_THREAD_STACK_INFO, "===== Thread stack information ====="},
    {PLUM_MAIN_STACK_INFO, "====== Main stack information ======"},
    {PLUM_THREAD_STACK_OVERFLOW,"Error: Thread stack(%08x) was overflow"},
    {PLUM_MAIN_STACK_OVERFLOW,"Error: Main stack(%08x) was overflow"},
    {PLUM_CALL_STACK_INFO, "Show more call stack info by run: addr2line -e %s%s -a -f %s"},
    {PLUM_CALL_STACK_ERR, "Dump call stack has an error"},
    {PLUM_FAULT_ON_THREAD, "Fault on thread %s"},
    {PLUM_FAULT_ON_HANDLER, "Fault on interrupt or bare metal(no OS) environment"},
    {PLUM_REGS_TITLE, "=================== Registers information ===================="},
    {PLUM_HFSR_VECTBL, "Hard fault is caused by failed vector fetch"},
    {PLUM_MFSR_IACCVIOL, "Memory management fault is caused by instruction access violation"},
    {PLUM_MFSR_DACCVIOL, "Memory management fault is caused by data access violation"},
    {PLUM_MFSR_MUNSTKERR, "Memory management fault is caused by unstacking error"},
    {PLUM_MFSR_MSTKERR, "Memory management fault is caused by stacking error"},
    {PLUM_MFSR_MLSPERR, "Memory management fault is caused by floating-point lazy state preservation"},
    {PLUM_BFSR_IBUSERR, "Bus fault is caused by instruction access violation"},
    {PLUM_BFSR_PRECISERR, "Bus fault is caused by precise data access violation"},
    {PLUM_BFSR_IMPREISERR, "Bus fault is caused by imprecise data access violation"},
    {PLUM_BFSR_UNSTKERR, "Bus fault is caused by unstacking error"},
    {PLUM_BFSR_STKERR, "Bus fault is caused by stacking error"},
    {PLUM_BFSR_LSPERR, "Bus fault is caused by floating-point lazy state preservation"},
    {PLUM_UFSR_UNDEFINSTR, "Usage fault is caused by attempts to execute an undefined instruction"},
    {PLUM_UFSR_INVSTATE, "Usage fault is caused by attempts to switch to an invalid state (e.g., ARM)"},
    {PLUM_UFSR_INVPC, "Usage fault is caused by attempts to do an exception with a bad value in the EXC_RETURN number"},
    {PLUM_UFSR_NOCP, "Usage fault is caused by attempts to execute a coprocessor instruction"},
#if (CPU_PLATFORM_TYPE == CORTEX_M33)
    {PLUM_UFSR_STKOF, "Usage fault is caused by indicates that a stack overflow (hardware check) has taken place"},
#endif
    {PLUM_UFSR_UNALIGNED, "Usage fault is caused by indicates that an unaligned access fault has taken place"},
    {PLUM_UFSR_DIVBYZERO0, "Usage fault is caused by Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set)"},
    {PLUM_DFSR_HALTED, "Debug fault is caused by halt requested in NVIC"},
    {PLUM_DFSR_BKPT, "Debug fault is caused by BKPT instruction executed"},
    {PLUM_DFSR_DWTTRAP, "Debug fault is caused by DWT match occurred"},
    {PLUM_DFSR_VCATCH, "Debug fault is caused by Vector fetch occurred"},
    {PLUM_DFSR_EXTERNAL, "Debug fault is caused by EDBGRQ signal asserted"},
    {PLUM_MMAR, "The memory management fault occurred address is %08x"},
    {PLUM_BFAR, "The bus fault occurred address is %08x"},
} ;

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

/**
 * @brief:  check the disassembly instruction is 'BL' or 'BLX'
 * 
 * @param[in]:  addr    -   
 * 
 * @note:   
 */
PLUM_PRIVATE
plum_bit plum_trace_ins_is_bl_blx(plum_u32 addr) 
{
    plum_u16 ins1 = *((plum_u16 *)addr);
    plum_u16 ins2 = *((plum_u16 *)(addr + 2));

#define BL_INS_MASK  0xF800
#define BL_INS_HIGH  0xF800
#define BL_INS_LOW   0xF000
#define BLX_INX_MASK 0xFF00
#define BLX_INX      0x4700

    if ((ins2 & BL_INS_MASK) == BL_INS_HIGH && (ins1 & BL_INS_MASK) == BL_INS_LOW) {
        return plum_true;
    } else if ((ins2 & BLX_INX_MASK) == BLX_INX) {
        return plum_true;
    } else {
        return plum_false;
    }
}

/**
 * @brief:  backtrace function call stack
 *
 * @param[in]: buffer   -   call stack buffer
 * @param[in]: size     -   buffer size
 * @param[in]: sp       -   stack pointer
 *
 * @return depth
 */
size_t plum_trace_callstack(plum_u32 *buffer, size_t size, plum_u32 sp) 
{
    plum_u32 stack_start_addr = main_stack_start_addr, pc;
    size_t depth = 0, stack_size = main_stack_size;
    bool regs_saved_lr_is_valid = false;

    if (on_fault) {
        if (!stack_is_overflow) {
            /* first depth is PC */
            buffer[depth++] = regs.saved.pc;
            /* fix the LR address in thumb mode */
            pc = regs.saved.lr - 1;
            if ((pc >= code_start_addr) && (pc <= code_start_addr + code_size) && (depth < CMB_CALL_STACK_MAX_DEPTH)
                    && (depth < size)) {
                buffer[depth++] = pc;
                regs_saved_lr_is_valid = true;
            }
        }
    }

    if (stack_is_overflow) {
        if (sp < stack_start_addr) {
            sp = stack_start_addr;
        } else if (sp > stack_start_addr + stack_size) {
            sp = stack_start_addr + stack_size;
        }
    }

    /* copy called function address */
    for (; sp < stack_start_addr + stack_size; sp += sizeof(size_t)) {
        /* the *sp value may be LR, so need decrease a word to PC */
        pc = *((plum_u32 *) sp) - sizeof(size_t);
        /* the Cortex-M using thumb instruction, so the pc must be an odd number */
        if (pc % 2 == 0) {
            continue;
        }
        /* fix the PC address in thumb mode */
        pc = *((plum_u32 *) sp) - 1;
        if ((pc >= code_start_addr + sizeof(size_t)) && (pc <= code_start_addr + code_size) && (depth < CMB_CALL_STACK_MAX_DEPTH)
                /* check the the instruction before PC address is 'BL' or 'BLX' */
                && plum_trace_ins_is_bl_blx(pc - sizeof(size_t)) && (depth < size)) {
            /* the second depth function may be already saved, so need ignore repeat */
            if ((depth == 2) && regs_saved_lr_is_valid && (pc == buffer[1])) {
                continue;
            }
            buffer[depth++] = pc;
        }
    }

    return depth;
}
/*****************************************************************************
 * Public Functions
 ****************************************************************************/

/**
 * @brief:  backtrace for fault
 *
 * @param[in]: fault_handler_lr     -   the LR register value on fault handler
 * @param[in]: fault_handler_sp     -   the stack pointer on fault handler
 * 
 * @note: only call once
 */
plum_void cm_backtrace_fault(plum_u32 fault_handler_lr, plum_u32 fault_handler_sp) 
{
    print = plum_kernel_log_callback();

    plum_u32 stack_pointer = fault_handler_sp;
    plum_u32 saved_regs_addr = stack_pointer;
    const char *regs_name[] = { "R0 ", "R1 ", "R2 ", "R3 ", "R12", "LR ", "PC ", "PSR" };

    /* delete saved R0~R3, R12, LR,PC,xPSR registers space */
    stack_pointer += sizeof(size_t) * 8;

#if (CPU_PLATFORM_TYPE == CORTEX_M4) || (CPU_PLATFORM_TYPE == CORTEX_M7) || \
    (CPU_PLATFORM_TYPE == CORTEX_M33)
    stack_pointer = statck_del_fpu_regs(fault_handler_lr, stack_pointer);
#endif /* (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M4) || \
          (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M7) */

#ifdef CMB_USING_DUMP_STACK_INFO
    /* check stack overflow */
    if (stack_pointer < stack_start_addr || stack_pointer > stack_start_addr + stack_size) {
        stack_is_overflow = true;
    }
    /* dump stack information */
    dump_stack(stack_start_addr, stack_size, (plum_u32 *) stack_pointer);
#endif /* CMB_USING_DUMP_STACK_INFO */

        /* dump register */
        cmb_PLUMln(PLUM_info[PLUM_REGS_TITLE]);

        regs.saved.r0        = ((plum_u32 *)saved_regs_addr)[0];  // Register R0
        regs.saved.r1        = ((plum_u32 *)saved_regs_addr)[1];  // Register R1
        regs.saved.r2        = ((plum_u32 *)saved_regs_addr)[2];  // Register R2
        regs.saved.r3        = ((plum_u32 *)saved_regs_addr)[3];  // Register R3
        regs.saved.r12       = ((plum_u32 *)saved_regs_addr)[4];  // Register R12
        regs.saved.lr        = ((plum_u32 *)saved_regs_addr)[5];  // Link register LR
        regs.saved.pc        = ((plum_u32 *)saved_regs_addr)[6];  // Program counter PC
        regs.saved.psr.value = ((plum_u32 *)saved_regs_addr)[7];  // Program status word PSR

        print("  %s: %08x  %s: %08x  %s: %08x  %s: %08x", regs_name[0], regs.saved.r0,
                                                                regs_name[1], regs.saved.r1,
                                                                regs_name[2], regs.saved.r2,
                                                                regs_name[3], regs.saved.r3);
        print("  %s: %08x  %s: %08x  %s: %08x  %s: %08x", regs_name[4], regs.saved.r12,
                                                                regs_name[5], regs.saved.lr,
                                                                regs_name[6], regs.saved.pc,
                                                                regs_name[7], regs.saved.psr.value);
        print("==============================================================");

    /* the Cortex-M0 is not support fault diagnosis */
#if (CMB_CPU_PLATFORM_TYPE != CMB_CPU_ARM_CORTEX_M0)
    regs.syshndctrl.value = CMB_SYSHND_CTRL;  // System Handler Control and State Register
    regs.mfsr.value       = CMB_NVIC_MFSR;    // Memory Fault Status Register
    regs.mmar             = CMB_NVIC_MMAR;    // Memory Management Fault Address Register
    regs.bfsr.value       = CMB_NVIC_BFSR;    // Bus Fault Status Register
    regs.bfar             = CMB_NVIC_BFAR;    // Bus Fault Manage Address Register
    regs.ufsr.value       = CMB_NVIC_UFSR;    // Usage Fault Status Register
    regs.hfsr.value       = CMB_NVIC_HFSR;    // Hard Fault Status Register
    regs.dfsr.value       = CMB_NVIC_DFSR;    // Debug Fault Status Register
    regs.afsr             = CMB_NVIC_AFSR;    // Auxiliary Fault Status Register

    fault_diagnosis();
#endif

    PLUM_call_stack(stack_pointer);
}
#endif
/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
